package com.sychip.fhc.lib.mqtt

import android.content.Context
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended
import org.eclipse.paho.client.mqttv3.MqttClient
import org.eclipse.paho.client.mqttv3.MqttConnectOptions
import org.eclipse.paho.client.mqttv3.MqttException
import org.eclipse.paho.client.mqttv3.MqttMessage
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence
import timber.log.Timber
import java.io.BufferedInputStream
import java.io.InputStream
import java.io.InputStreamReader
import java.security.KeyPair
import java.security.KeyStore
import java.security.SecureRandom
import java.security.Security
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory

class AwsMqtt(private val context: Context) {
    private lateinit var mSampleClient: MqttClient
    private val mPersistence = MemoryPersistence()
    val callList = mutableListOf<MqttCallbackExtended>()
    fun readAwsFilesFromAssets(context: Context): Triple<InputStream, InputStream, InputStream> {
        val assetManager = context.assets
        return Triple(
            assetManager.open("aws/aws_iot_root_ca.pem"),
            assetManager.open("aws/aws_iot_certificate_pem.pem"),
            assetManager.open("aws/aws_iot_private_pem.pem")
        )
    }
    fun connect(): MqttClient {
        try {
            val (mCaCrtFile, mCrtFile, mKeyFile) = readAwsFilesFromAssets(context)
//            val mCaCrtFile = context.resources.openRawResource(R.raw.aws_iot_root_ca)
//            val mCrtFile = context.resources.openRawResource(R.raw.aws_iot_certificate_pem)
//            val mKeyFile = context.resources.openRawResource(R.raw.aws_iot_private_pem)
            val mBroker = "ssl://a1viw0dtcg0nph-ats.iot.us-east-1.amazonaws.com:8883"
            val mClientId = "android_client_snx"

            mSampleClient = MqttClient(mBroker, mClientId, mPersistence)
            val connOpts = MqttConnectOptions().apply {
                serverURIs = arrayOf(mBroker)
                socketFactory = getSocketFactory(mCaCrtFile, mCrtFile, mKeyFile)
                isCleanSession = false
                keepAliveInterval = 60
                isAutomaticReconnect = true
            }

            mSampleClient.setCallback(createMqttCallback())
            mSampleClient.connect(connOpts)
        } catch (e: Exception) {
            Timber.e("connect error: %s", e.message)
        }
        return mSampleClient
    }

    private fun createMqttCallback() = object : MqttCallbackExtended {
        override fun connectComplete(reconnect: Boolean, serverURI: String) {
            callList.forEach { it.connectComplete(reconnect, serverURI) }
            Timber.i("connect success")
            try {
                mSampleClient.subscribe(arrayOf(
                    "murata_iot/example/topic",
                    "murata_baby/example/topic"
                ), intArrayOf(1, 1))
            } catch (e: Exception) {
                Timber.i("subscribe error: %s", e.message)
            }
        }

        override fun connectionLost(cause: Throwable) {
            Timber.i("connection lost")
            callList.forEach { it.connectionLost(cause) }
        }

        override fun messageArrived(topic: String?, message: MqttMessage) {
            Timber.e("Topic:$topic\nMessage: ${String(message.payload)}")
            callList.forEach {
                try { it.messageArrived(topic, message) }
                catch (e: Exception) { e.printStackTrace() }
            }
        }

        override fun deliveryComplete(token: IMqttDeliveryToken) {
            Timber.e("deliveryComplete:${token.messageId}")
        }
    }

    private fun getSocketFactory(
        caCrtFile: InputStream,
        crtFile: InputStream,
        keyFile: InputStream
    ): SSLSocketFactory {
        Security.addProvider(BouncyCastleProvider())
        val (caCert, cert) = loadCertificates(caCrtFile, crtFile)
        val keyPair = loadPrivateKey(keyFile)

        return createSSLContext(caCert, cert, keyPair).socketFactory
    }

    private fun loadCertificates(
        caCrtFile: InputStream,
        crtFile: InputStream
    ): Pair<X509Certificate, X509Certificate> {
        val cf = CertificateFactory.getInstance("X.509")
        val caCert = BufferedInputStream(caCrtFile).use { bis ->
            cf.generateCertificate(bis) as X509Certificate
        }
        val cert = BufferedInputStream(crtFile).use { bis ->
            cf.generateCertificate(bis) as X509Certificate
        }
        return Pair(caCert, cert)
    }

    private fun loadPrivateKey(keyFile: InputStream): KeyPair {
        return PEMParser(InputStreamReader(keyFile)).use { pemParser ->
            JcaPEMKeyConverter().getKeyPair(pemParser.readObject() as PEMKeyPair)
        }
    }

    private fun createSSLContext(
        caCert: X509Certificate,
        cert: X509Certificate,
        keyPair: KeyPair
    ): SSLContext {
        val caKs = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
            load(null, null)
            setCertificateEntry("cert-certificate", caCert)
        }

        val ks = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
            load(null, null)
            setCertificateEntry("certificate", cert)
            setKeyEntry("private-cert", keyPair.private, charArrayOf(), arrayOf(cert))
        }

        val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
            init(caKs)
        }

        val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
            init(ks, charArrayOf())
        }

        return SSLContext.getInstance("TLSv1.2").apply {
            init(kmf.keyManagers, tmf.trustManagers, SecureRandom())
        }
    }

    // 获取指定格式的时间字符串（带时区）
    fun getCurrentTime(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
        return SimpleDateFormat(pattern, Locale.getDefault()).apply {
            timeZone = TimeZone.getDefault() // 使用系统默认时区
        }.format(Date())
    }

    fun subscribeTopic(topic: String, qos: Int) {
        try {
            mSampleClient.subscribe(topic, qos) { t, msg ->
                Timber.e("""
                    Received message
                    Time: ${getCurrentTime()}
                    Topic: $t
                    Message: ${String(msg.payload)}
                """.trimIndent())
            }
        } catch (e: MqttException) {
            Timber.e("subscribe error: ${e.message}")
        }
    }

    fun unsubscribeTopic(topic: String) {
        try {
            mSampleClient.unsubscribe(topic)
        } catch (e: MqttException) {
            Timber.e("unsubscribe error: ${e.message}")
        }
    }

    fun addCallback(callback: MqttCallbackExtended) = callList.add(callback)
    fun clearCallback() = callList.clear()
}